/*
 * GContestElement.java
 * Created on 27 juin 2007
 *
 * Glacéo Internet Platform
 * http://sourceforge.net/projects/chm/
 *
 * Copyright (c) 2005-2007, Jan Janke (VirtualHockey Project)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hockeo.vhbase.data.dao.contest;


import java.util.List;
import java.util.Map;

import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MapKey;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table;

import com.hockeo.vhbase.data.dao.IGEntity;
import com.hockeo.vhbase.data.dao.season.GMatchday;
import com.hockeo.vhbase.data.dao.season.GMoveday;
import com.hockeo.vhbase.data.dao.user.GComputerUser;
import com.hockeo.vhbase.error.GNotImplementedException;
import com.hockeo.vhbase.seasoncycle.movemaking.IGMoveMaker;

/**
 * Base class for all classes implementing a particular competition format (e.g. a KO or a
 * round robin round).
 *
 * @version $Id$
 * @author jjanke
 */
@Entity
@Table( name = "m_contest_element" )
@Inheritance( strategy = InheritanceType.SINGLE_TABLE )
@DiscriminatorColumn( name = "type", discriminatorType = DiscriminatorType.STRING )
public abstract class GContestElement implements IGEntity, Comparable<GContestElement>
{
  @Id
  @Column( name = "id" )
  private String                  d_strId;

  @ManyToOne( fetch = FetchType.LAZY )
  @JoinColumn( name = "contest_round_id" )
  private GContestRound           d_contestRound;

  /**
   * The label may e.g. be the letter or number designating a particular round robin pool
   * or the name (like 'quarter final', 'semi final' etc.) of a KO round. Please be aware
   * that the contained text may just be a reference to a resource defined somewhere else
   * and containing the actual localized label.
   */
  @Column( name = "label" )
  private String                  d_strLabel;

  @OneToMany( mappedBy = "d_contestElement" )
  @MapKey( name = "d_nNum" )
  @OrderBy( "d_nNum" )
  private Map<Integer, GMatchday> d_mapMatchdays;

  /** Default parameterless constructor. */
  protected GContestElement()
  {}

  /**
   * Constructor to be used by sub classes that are created dynamically at the beginning
   * of each season.
   *
   * @param parent the parent contest round
   * @param strId the ID of the new contest element (attention: must be unique)
   * @param strLabel the label or the resource key for the new contest element
   */
  protected GContestElement( GContestRound parent, String strId, String strLabel )
  {
    d_contestRound = parent;
    d_strId = strId;
    d_strLabel = strLabel;
  }

  /**
   * @return Returns the number of clubs that are actively participating (at least one
   *         match) in this contest element.
   */
  public abstract int getNumTeams();

  /**
   * @return Returns the number of matchdays which must be played to complete the entire
   *         contest element.
   */
  public abstract int getNumMatchdays();

  /**
   * @return Returns the minimum number of movedays that are required to play all
   *         matchdays of this contest element.
   */
  public abstract int getMinNumMovedays();

  /**
   * Attaches the given matchdays to the also given movedays in such a manner that the
   * days are equally distributed and respect the requirements of the specific contest
   * element (e.g. for a KO element, all matchdays of one round must be attached to the
   * same moveday).
   *
   * @param listMovedays list that contains all the movedays to which the matchdays should
   *          be attached
   * @param listMatchdays list of all the matchdays that should be attached to the
   *          different movedays
   * @param fUseMiddleMoveday indicates whether the middle moveday in the list of passed
   *          movedays should be used to assign matchdays or not
   */
  public abstract void attachMatchdaysToMovedays( List<GMoveday> listMovedays, List<GMatchday> listMatchdays,
      boolean fUseMiddleMoveday );

  /**
   * Returns the name of the move-making implementation to be used for this contest
   * element and the given user.
   *
   * @param user the computer user for which a matching move-making implementation name
   *          should be returned
   * @return the name of the bean implementing the {@link IGMoveMaker} interface
   */
  public abstract String getMoveMakingImplName( GComputerUser user );

  /**
   * Returns the amount of matches to be played by one club during this contest element.
   * For contest elements where the number of matches may vary, this method is not
   * implemented and throws a {@link GNotImplementedException}.
   *
   * @return the number of matches to be played by one club
   * @throws GNotImplementedException if the number of matches to be played by one club
   *           cannot be exactly determined
   */
  public int getNumMatchesPerClub()
  {
    throw new GNotImplementedException( "The number of matches for one club cannot be determined exactly." );
  }

  /**
   * @return Indicates whether this contest element may be better organised if more than
   *         the strict minimum number of movedays is allocated to it. By default
   *         <code>false</code> is returned.
   */
  public boolean prefersAdditionalMovedays()
  {
    return false;
  }

  public GContest getContest()
  {
    return getContestRound().getContest();
  }

  public GContestRound getContestRound()
  {
    return d_contestRound;
  }

  /**
   * Allows to set the parent contest round. This method is intentionally package visible
   * only.
   *
   * @param parent the round to which this contest element is attached
   */
  void setContestRound( GContestRound parent )
  {
    d_contestRound = parent;
  }

  public String getLabel()
  {
    return d_strLabel;
  }

  /**
   * @return Returns a map of all matchdays where the key is the matchday number.
   */
  public Map<Integer, GMatchday> getMatchdays()
  {
    return d_mapMatchdays;
  }

  /**
   * Returns the matchday with the given number.
   *
   * @param nNum the number of the matchday to be fetched
   * @return the requested matchday
   */
  public GMatchday getMatchday( int nNum )
  {
    return d_mapMatchdays.get( Integer.valueOf( nNum ) );
  }

  public String getId()
  {
    return d_strId;
  }

  public boolean instanceOf( Class<? extends IGEntity> classOther )
  {
    return classOther.isAssignableFrom( getGEntityClass() );
  }

  /*
   * @see java.lang.Object#equals(java.lang.Object)
   */
  @Override
  public boolean equals( Object obj )
  {
    try
    {
      return d_strId.equals( ( (GContestElement) obj ).getId() );
    }
    catch ( Exception ex ) // especially ClassCastException(s) may happen
    {
      return false;
    }
  }

  /*
   * @see java.lang.Object#hashCode()
   */
  @Override
  public int hashCode()
  {
    return d_strId.hashCode();
  }

  /*
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString()
  {
    return d_strId;
  }

  /**
   * Orders by contest round and element ID.
   */
  public int compareTo( GContestElement other )
  {
    int nTmp = this.getContestRound().compareTo( other.getContestRound() );
    if ( nTmp != 0 )
      return nTmp;

    return this.getId().compareTo( other.getId() );
  }
}
